<?php
/**
 * Created by PhpStorm.
 * User: Kevin
 * Date: 2017/12/20
 * Time: 10:43
 */

namespace App\Extend\Tencent;

use Exception;
use GuzzleHttp\Client;
use Mdanter\Ecc\Crypto\Signature\Signer;
use Mdanter\Ecc\Crypto\Signature\SignHasher;
use Mdanter\Ecc\EccFactory;
use Mdanter\Ecc\Serializer\PrivateKey\DerPrivateKeySerializer;
use Mdanter\Ecc\Serializer\PrivateKey\PemPrivateKeySerializer;
use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;

class Im
{

    private $private_key = false;
    private $appid = 0;

    /**
     * 设置Appid
     * @param string $appid
     * @return bool
     */
    public function setAppid($appid)
    {
        $this->appid = $appid;

        return true;
    }

    /**
     * 设置私钥 如果要生成usersig则需要私钥
     * @param string $private_key 私钥文件内容
     * @return bool 是否成功
     */
    public function setPrivateKey($private_key)
    {
        $this->private_key = $private_key;

        return true;
    }

    /**
     * 用于url的base64encode '+' => '*', '/' => '-', '=' => '_'
     * @param string $string 需要编码的数据
     * @return string 编码后的base64串，失败返回false
     * @throws Exception
     */
    private function base64Encode($string)
    {
        static $replace = ['+' => '*', '/' => '-', '=' => '_'];
        $base64 = base64_encode($string);
        if ($base64 === false) {
            throw new Exception('base64_encode error');
        }

        return str_replace(array_keys($replace), array_values($replace), $base64);
    }

    /**
     * 根据json内容生成需要签名的buf串
     * @param array $json 票据json对象
     * @return string 按标准格式生成的用于签名的字符串 失败时返回false
     * @throws Exception
     */
    private function genSignContent(array $json)
    {
        static $members = [
            'TLS.appid_at_3rd',
            'TLS.account_type',
            'TLS.identifier',
            'TLS.sdk_appid',
            'TLS.time',
            'TLS.expire_after',
        ];
        $content = '';
        foreach ($members as $member) {
            if (!isset($json[$member])) {
                throw new Exception('json need ' . $member);
            }
            $content .= "{$member}:{$json[$member]}\n";
        }

        return $content;
    }

    /**
     * ECDSA-SHA256签名
     * @param string $data 需要签名的数据
     * @return string 返回签名 失败时返回false
     */
    private function sign($data)
    {

        $adapter                   = EccFactory::getAdapter();
        $generator                 = EccFactory::getNistCurves()->generator256();
        $useDerandomizedSignatures = true;
        $algorithm                 = 'sha256';

        ## You'll be restoring from a key, as opposed to generating one.
        $pemSerializer = new PemPrivateKeySerializer(new DerPrivateKeySerializer($adapter));
        $key           = $pemSerializer->parse($this->private_key);
        $signer        = new Signer($adapter);

        $hasher = new SignHasher($algorithm, $adapter);
        $hash   = $hasher->makeHash($data, $generator);

        # Derandomized signatures are not necessary, but can reduce
        # the attack surface for a private key that is to be used often.
        if ($useDerandomizedSignatures) {
            $random = \Mdanter\Ecc\Random\RandomGeneratorFactory::getHmacRandomGenerator($key, $hash, $algorithm);
        } else {
            $random = \Mdanter\Ecc\Random\RandomGeneratorFactory::getRandomGenerator();
        }
        $randomK    = $random->generate($generator->getOrder());
        $signature  = $signer->sign($key, $hash, $randomK);
        $serializer = new DerSignatureSerializer();

        return $serializer->serialize($signature);
    }

    /**
     * 生成usersig
     * @param string    $identifier 用户名
     * @param float|int $expire     usersig有效期 默认为360天
     * @return string 生成的UserSig 失败时为false
     * @throws Exception
     */
    public function genSig($identifier, $expire = 360 * 86400)
    {
        $json            = [
            'TLS.account_type' => '0',
            'TLS.identifier'   => (string)$identifier,
            'TLS.appid_at_3rd' => '0',
            'TLS.sdk_appid'    => (string)$this->appid,
            'TLS.expire_after' => (string)$expire,
            'TLS.version'      => '201512300000',
            'TLS.time'         => (string)time(),
        ];
        $err             = '';
        $content         = $this->genSignContent($json, $err);
        $signature       = $this->sign($content);
        $json['TLS.sig'] = base64_encode($signature);
        if ($json['TLS.sig'] === false) {
            throw new Exception('base64_encode error');
        }
        $json_text = json_encode($json);
        if ($json_text === false) {
            throw new Exception('json_encode error');
        }
        $compressed = gzcompress($json_text);
        if ($compressed === false) {
            throw new Exception('gzcompress error');
        }

        return $this->base64Encode($compressed);
    }

    /**
     * 获取UserSig
     * @param $user
     * @return string
     */
    public function userSig($user)
    {
        // 设置在腾讯云申请的appid
        $this->setAppid(config('common.im.app_id'));
        // 生成user sig需要先设置私钥
        $this->setPrivateKey(config('common.im.private_key'));

        // 生成user sig
        $sig = '';
        try {
            $sig = $this->genSig($user);
        } catch (Exception $exception) {
            abort(400, $exception->getMessage());
        }

        return $sig;
    }

    /**
     * 添加好友
     * @param $identifier
     * @param $to_identifier
     * @return bool
     */
    public function add($identifier, $to_identifier)
    {
        $client = new Client();

        $url = 'https://console.tim.qq.com/v4/sns/friend_add?usersig=' . $this->userSig('halobear') . '&identifier=halobear&sdkappid=' . $this->appid . '&random=&contenttype=json';

        $body    = [
            'From_Account'  => $identifier,
            'AddFriendItem' => [
                [
                    'To_Account' => $to_identifier,
                    'AddSource'  => 'AddSource_Type_System',
                ],
            ],
        ];
        $res     = $client->request('POST', $url, ['body' => \GuzzleHttp\json_encode($body)]);
        $content = $res->getBody()->getContents();
        write_log('im', $content);

        return true;
    }

    /**
     * 修改用户资料
     * @param $identifier
     * @param $info
     * @return bool
     */
    public function profile($identifier, $info)
    {
        $client = new Client();

        $url = 'https://console.tim.qq.com/v4/profile/portrait_set?usersig=' . $this->userSig('halobear') . '&identifier=halobear&sdkappid=' . $this->appid . '&random=' . time() . '&contenttype=json';

        $body    = [
            'From_Account' => $identifier,
            'ProfileItem'  => [
                [
                    'Tag'   => 'Tag_Profile_IM_Nick',
                    'Value' => $info['name'],
                ],
                [
                    'Tag'   => 'Tag_Profile_IM_SelfSignature',
                    'Value' => (string)$info['phone'],
                ],
            ],
        ];
        $res     = $client->request('POST', $url, ['body' => \GuzzleHttp\json_encode($body)]);
        $content = $res->getBody()->getContents();
        write_log('im', $body);
        write_log('im', $content);

        return true;
    }

}
